package com.jzo2o.orders.manager.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.customer.dto.response.AddressBookResDTO;
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.api.trade.NativePayApi;
import com.jzo2o.api.trade.TradingApi;
import com.jzo2o.api.trade.dto.request.NativePayReqDTO;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.dto.response.TradingResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.api.trade.enums.TradingStateEnum;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.model.msg.TradeStatusMsg;
import com.jzo2o.common.utils.DateUtils;
import com.jzo2o.common.utils.NumberUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.orders.base.constants.RedisConstants;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.manager.model.dto.request.OrdersPayReqDTO;
import com.jzo2o.orders.manager.model.dto.request.PlaceOrderReqDTO;
import com.jzo2o.orders.manager.model.dto.response.OrdersPayResDTO;
import com.jzo2o.orders.manager.model.dto.response.PlaceOrderResDTO;
import com.jzo2o.orders.manager.porperties.TradeProperties;
import com.jzo2o.orders.manager.service.IOrdersCreateService;
import com.jzo2o.orders.manager.service.client.CustomerClient;
import com.jzo2o.orders.manager.service.client.MarketClient;
import com.jzo2o.orders.manager.service.client.ServeClient;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

/**
 * <p>
 * 下单服务类
 * </p>
 *
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {

    @Resource
    private CustomerClient customerClient;

    @Resource
    private ServeClient serveClient;

    @Resource
    private RedisTemplate<String,Long> redisTemplate;

    @Resource
    private IOrdersCreateService proxy;

    @Resource
    private TradeProperties tradeProperties;

    @Resource
    private NativePayApi nativePayApi;

    @Resource
    private TradingApi tradingApi;

    @Resource
    private MarketClient marketClient;

    /**
     * 新建订单
     * @param dto 下单的参数
     */
    @Override
    public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO dto) {

        //1. 获取地址薄信息：远程调用customer服务，传参地址薄id，获取地址薄信息
        AddressBookResDTO addressBook = customerClient.findAddressBookById(dto.getAddressBookId());
        if (addressBook == null) {
            throw new BadRequestException("预约地址异常");
        }
        //2. 获取服务信息：远程调用foundations服务，传参区域服务id，获取服务项和服务信息
        ServeAggregationResDTO serve = serveClient.findServeById(dto.getServeId());
        if (serve == null) {
            throw new BadRequestException("服务不可用");
        }
        //3. 生成订单号：生成规则 yyMMdd + 13位序列号
        Long orderId = generateOrderId();
        //4. 计算价格

        //5. 组装订单信息，存入订单表
        Orders order = new Orders();
        order.setId(orderId);
        order.setUserId(UserContext.currentUserId());
        order.setServeTypeId(serve.getServeTypeId());
        order.setServeTypeName(serve.getServeTypeName());
        order.setServeItemId(serve.getServeItemId());
        order.setServeItemName(serve.getServeItemName());
        order.setServeItemImg(serve.getServeItemImg());
        order.setUnit(serve.getUnit());
        order.setServeId(serve.getId());
        order.setPayStatus(OrderPayStatusEnum.NO_PAY.getStatus());
        order.setOrdersStatus(OrderStatusEnum.NO_PAY.getStatus());
        order.setPrice(serve.getPrice());
        order.setPurNum(NumberUtils.null2Default(dto.getPurNum(), 1));
        order.setTotalAmount(serve.getPrice().multiply(new BigDecimal(order.getPurNum())));
        order.setDiscountAmount(BigDecimal.ZERO);
        order.setRealPayAmount(NumberUtils.sub(order.getTotalAmount(), order.getDiscountAmount()));
        order.setCityCode(serve.getCityCode());
        order.setServeAddress(addressBook.getAddress());
        order.setContactsPhone(addressBook.getPhone());
        order.setContactsName(addressBook.getName());
        order.setServeStartTime(dto.getServeStartTime());
        order.setLon(addressBook.getLon());
        order.setLat(addressBook.getLat());
        order.setSortBy(DateUtils.toEpochMilli(order.getServeStartTime()) + orderId % 1000000);
        //5. 保存到订单表里
        if (ObjectUtils.isNull(dto.getCouponId())) {
            //下单时没有选择优惠券，直接保存订单
            proxy.add(order);
        }else{
            //下单时选择了优惠券，核销优惠券并计算优惠金额，然后再保存订单
            proxy.addWithCoupon(order, dto.getCouponId());
        }


        //6. 构造返回值
        PlaceOrderResDTO res = new PlaceOrderResDTO();
        res.setId(orderId);
        return res;
    }

    /**
     * @param order 实现事务时不至于超时
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(Orders order) {
        boolean saved = save(order);
        if (!saved) {
            throw new DBException("下单失败");
        }
    }

    /**
     * 与第三方服务对接
     * @param orderId 业务订单id
     * @param dto 支付渠道信息
     */
    @Override
    public OrdersPayResDTO pay(Long orderId, OrdersPayReqDTO dto) {
        //首先查询订单是否存在
        Orders orders = getById(orderId);
        if (orders==null) {
            throw new CommonException("订单不存在");
        }
        //如果订单是已支付成功状态、且交易单号非空，说明确实是已支付状态，直接返回结果
        //相当于二周目
        if (orders.getPayStatus()== OrderPayStatusEnum.PAY_SUCCESS.getStatus()
        && orders.getTradingOrderNo()!=null) {
            OrdersPayResDTO res = BeanUtil.toBean(orders, OrdersPayResDTO.class);
            res.setProductOrderNo(orders.getId());
            return res;
        }
        //组装返回的参数
        NativePayReqDTO nativePayReqDTO=new NativePayReqDTO();
        nativePayReqDTO.setProductOrderNo(orders.getId());
        nativePayReqDTO.setTradingAmount(orders.getRealPayAmount());
        //是否改变支付渠道
        nativePayReqDTO.setChangeChannel(!ObjectUtils.equal(orders.getTradingChannel(), dto.getTradingChannel().getValue()));
        //支付渠道
        nativePayReqDTO.setTradingChannel(dto.getTradingChannel());
        //获取支付渠道的id
        nativePayReqDTO.setEnterpriseId(ObjectUtils.equal(PayChannelEnum.ALI_PAY, dto.getTradingChannel())?tradeProperties.getAliEnterpriseId():tradeProperties.getWechatEnterpriseId());
        nativePayReqDTO.setProductAppId("jzo2o.orders");
        nativePayReqDTO.setMemo(orders.getServeItemName());
        //调用交易服务，获取二维码和交易单信息
        NativePayResDTO trading = nativePayApi.createDownLineTrading(nativePayReqDTO);
        log.info("订单{}请求支付，生成二维码：{}", orderId, trading.getQrCode());
        //调用交易服务成功，将交易单号和支付渠道更新到订单表里
        boolean update = lambdaUpdate()
                .set(Orders::getTradingChannel, trading.getTradingChannel())
                .set(Orders::getTradingOrderNo, trading.getTradingOrderNo())
                .eq(Orders::getId, trading.getProductOrderNo())
                .update();
        if (!update) {
            throw new CommonException("订单" + orders.getId() + "请求支付后更新订单异常");
        }
        //返回结果
        return BeanUtil.toBean(trading, OrdersPayResDTO.class);
    }

    /**
     * @param statusMsg 支付成功 更新状态
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void paySuccess(TradeStatusMsg statusMsg) {
        boolean updated = lambdaUpdate()
                .set(Orders::getPayTime, LocalDateTime.now())
                .set(Orders::getTradingChannel, statusMsg.getTradingChannel())
                .set(Orders::getTradingOrderNo, statusMsg.getTradingOrderNo())
                .set(Orders::getPayStatus, OrderPayStatusEnum.PAY_SUCCESS.getStatus())
                .set(Orders::getOrdersStatus, OrderStatusEnum.DISPATCHING.getStatus())
                .set(Orders::getTransactionId, statusMsg.getTransactionId())
                .eq(Orders::getId, statusMsg.getProductOrderNo())
                .update();
        if (!updated) {
            Long orderId = statusMsg.getProductOrderNo();
            throw new DBException("更新订单"+orderId+"支付状态失败");
        }
    }

    /**
     * 主动获取订单支付结果
     * @param id 业务订单id
     * @return 查询订单
     */
    @Override
    public OrdersPayResDTO payResult(Long id) {
        //先查询自己的业务订单数据
        Orders orders = getById(id);
        if (orders == null) {
            throw new CommonException("订单不存在");
        }

        //如果业务订单状态为未支付，则去交易服务中查询最新状态
        if (OrderPayStatusEnum.NO_PAY.getStatus() == orders.getPayStatus()
                && orders.getTradingOrderNo() != null) {
            //调用交易服务，由交易服务去第三方平台查询最新的状态
            TradingResDTO tradeResult = tradingApi.findTradResultByTradingOrderNo(orders.getTradingOrderNo());
            if (tradeResult != null && tradeResult.getTradingState()== TradingStateEnum.YJS) {
                //如果查到了交易结果且状态为已支付，就封装交易结果信息
                TradeStatusMsg statusMsg = TradeStatusMsg.builder()
                        .productOrderNo(orders.getId())
                        .tradingChannel(tradeResult.getTradingChannel())
                        .tradingOrderNo(tradeResult.getTradingOrderNo())
                        .transactionId(tradeResult.getTransactionId())
                        .build();
                //将交易状态结果信息更新到业务订单表里：通过当前类的代理对象调用，因为目标方法上有事务，要防止事务失效
                proxy.paySuccess(statusMsg);

                //构造返回值并直接返回
                OrdersPayResDTO res = BeanUtil.toBean(statusMsg, OrdersPayResDTO.class);
                res.setPayStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus());
                return res;
            }
        }

        //构造返回值并返回
        OrdersPayResDTO res = new OrdersPayResDTO();
        res.setTradingChannel(orders.getTradingChannel());
        res.setTradingOrderNo(orders.getTradingOrderNo());
        res.setProductOrderNo(orders.getId());
        res.setPayStatus(orders.getPayStatus());
        return res;
    }

    /**
     * 获取可用优惠券列表
     * @param serveId 服务id
     * @param purNum 购买数量
     */
    @Override
    public List<AvailableCouponsResDTO> getAvailableCoupons(Long serveId, Integer purNum) {
        //1. 获取服务信息
        ServeAggregationResDTO serve = serveClient.findServeById(serveId);
        //2为服务已上架
        if(serve==null || ObjectUtils.notEqual(serve.getSaleStatus(), 2)){
            throw new CommonException("服务不可用");
        }
        //2. 计算总金额
        BigDecimal totalAmount = serve.getPrice().multiply(new BigDecimal(purNum));

        //3. 获取可用的优惠券列表
        return marketClient.getAvailableCoupons(totalAmount);
    }

    /**
     * @param orders 订单信息
     * @param couponId 优惠券id
     */
    @Override
    @GlobalTransactional
    public void addWithCoupon(Orders orders, Long couponId) {
        //远程调用核销优惠券，获取优惠金额
        CouponUseReqDTO dto = new CouponUseReqDTO();
        dto.setId(couponId);
        dto.setOrdersId(orders.getId());
        dto.setTotalAmount(orders.getTotalAmount());
        CouponUseResDTO resDTO = marketClient.useCoupon(dto);
        //修改订单中的优惠金额
        orders.setDiscountAmount(resDTO.getDiscountAmount());
        //修改订单的实付金额
        orders.setRealPayAmount(orders.getTotalAmount().subtract(orders.getDiscountAmount()));
        //保存订单
        add(orders);
    }

    /**
     * @return 生成订单策略
     */
    private Long generateOrderId() {
        //通过Redis自增序列得到序号
        Long id = redisTemplate.opsForValue().increment(RedisConstants.Lock.ORDERS_SHARD_KEY_ID_GENERATOR, 1);
        //生成订单号   2位年+2位月+2位日+13位序号
       return (DateUtils.getFormatDate(LocalDateTime.now(), "yyMMdd") * 10000000000000L + id);
       // return id;
    }
}
